package notesDetection.GUI;

import javax.swing.*;
import javax.swing.plaf.basic.BasicBorders;

import notesDetection.MusicAnalyzer.AnalyzeMusicFile;
import notesDetection.MusicAnalyzer.AnalyzeMusicFile.AnalysisPreferences;
import notesDetection.Utilities.CompressionQualityEstimation;
import notesDetection.Utilities.GeneralNoteDetection.Note;
import notesDetection.Utilities.NoteInfo;
import notesDetection.Utilities.SegmentNotesInfo;
import notesDetection.wavPackage.WavExport;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;

/** 
*    This is the main class for running this project's GUI
*/
public class NotesJavaGUI {
	
	//	GUI Elements
	
    // Initialize all swing objects.
    private JFrame _mainFrame;
    private JPanel panelFileBrowse; 
    private JPanel pnlSouth;
    private JPanel pnlCenter;
    private JPanel pnlImage;
    private JPanel pnlSelectAlgorithmMethod;
    private JPanel headerPanel;
    private JPanel concertPitchPanel;
    private JLabel headerText;

    // Buttons!
    private JButton btnFileBrowser;
    private JButton btnRun;
    private JButton btnCancel;
    private JButton btnKill;

    private JRadioButton notesButton;
    private JRadioButton compressionButton;
    private ButtonGroup radioAlgorithmSelection;
    
    private JScrollPane scrollPaneTracer;
    private JTextArea textAreaTracer;
    
    private JTextField lastFilePath;
    private JTextField fieldConcertPitch;
    private JTextArea textConcertPitchWest;
    private JTextArea textConcertPitchEast;
    
    // Menu
    private JMenuBar menuBar;
    private JMenu menuFile;
    private JMenuItem menuItemQuit;
    private JMenu menuSettings;
    private JMenuItem menuItemNotes;
    private JMenuItem menuItemCompression;
    private JMenu menuHelp;
    private JMenuItem menuItemAbout;

    private NotesSettings _notesSettingsConfig;
    private CompressionSettings _compressionSettingsConfig;
    
    //	End of GUI Elements
    
    //	Other stuff
    
    private String _filename;
    private String _dir;
    
    //	Use actions names to match enum
    private String notesActionCommand = AnalyzeMusicFile.AnalysisPreferences.Notes.toString();
    private String compressActionCommand = AnalyzeMusicFile.AnalysisPreferences.Compression.toString();
    
    String _currentDir;
    
    private SwingWorker<List<SegmentNotesInfo>, Void> worker;
    private AnalyzeMusicFile analyzer;
    
    //	Constructor for the GUI
    public NotesJavaGUI(){
    	
        _mainFrame = new JFrame("Notes Detector"); //create Frame
        
        //	Initialize panels!
        
        panelFileBrowse = new JPanel(); // North quadrant 
        pnlSouth = new JPanel(); // South quadrant
        pnlCenter = new JPanel(); // Center quadrant
        pnlSelectAlgorithmMethod = new JPanel();
        
        //	Initialize buttons and other GUI Elements
        btnFileBrowser = new JButton("Browse...");
        lastFilePath = new JTextField();
        fieldConcertPitch = new JTextField("440");
        textConcertPitchWest = new JTextArea("A = ");
        textConcertPitchEast = new JTextArea(" Hz");
        textAreaTracer = new JTextArea();
        btnRun = new JButton("Run");
        btnCancel = new JButton("Cancel");
        btnKill = new JButton("Kill");
        
        //	Initialize RADIO!
        
        notesButton = new JRadioButton("Notes");
        notesButton.setSelected(true);
        notesButton.setMnemonic(KeyEvent.VK_N);
        notesButton.setActionCommand(notesActionCommand);

        compressionButton = new JRadioButton("Compression");
        compressionButton.setMnemonic(KeyEvent.VK_C);
        compressionButton.setActionCommand(compressActionCommand);

        //Group the radio buttons.
        radioAlgorithmSelection = new ButtonGroup();
        radioAlgorithmSelection.add(notesButton);
        radioAlgorithmSelection.add(compressionButton);
        
        pnlSelectAlgorithmMethod.add(notesButton);
        pnlSelectAlgorithmMethod.add(compressionButton);
        
        pnlSelectAlgorithmMethod.setPreferredSize(new Dimension(50, 50));
        
        //	Initialize Menu
        menuBar = new JMenuBar(); // Menu-bar
        menuFile = new JMenu("File"); // File Entry on Menu bar
        menuItemQuit = new JMenuItem("Quit"); // Quit sub item
        menuSettings = new JMenu("Settings");
        menuItemNotes = new JMenuItem("Notes Settings");
        menuItemCompression = new JMenuItem("Compression Settings");
        menuHelp = new JMenu("Help"); // Help Menu entry
        menuItemAbout = new JMenuItem("About"); // About Entry
    	
        //	Initialize others...
        _filename = "";
        _dir = "";
        _currentDir = new File("").getAbsolutePath();
        String iconRelativePath = "/notesDetection/GUI/resources/noteIcon.jpg";
        String logoRelativePath = "/notesDetection/GUI/resources/eighthLogo.png";
        
        
        pnlImage = new LogoImage(getClass().getResource(logoRelativePath));
        
        // Set menu-bar
    	_mainFrame.setJMenuBar(menuBar);
        
        //Build Menus
        menuFile.add(menuItemQuit);  // Create Quit line
        menuSettings.add(menuItemCompression);
        menuSettings.add(menuItemNotes);
        menuHelp.add(menuItemAbout); // Create About line
        menuBar.add(menuFile);        // Add Menu items to form
        menuBar.add(menuSettings);
        menuBar.add(menuHelp);
       
        lastFilePath.setPreferredSize(new Dimension(300, 20));
        lastFilePath.setEditable(false);

        textAreaTracer.setEditable(false);
        textAreaTracer.setBorder(new BasicBorders.ButtonBorder(Color.BLACK, Color.BLACK,Color.BLACK,Color.BLACK));
        scrollPaneTracer = new JScrollPane(textAreaTracer);
        
        scrollPaneTracer.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPaneTracer.setPreferredSize(new Dimension(500, 80));
        
        btnFileBrowser.addActionListener(new OpenFile());
        btnRun.addActionListener(new RunAlgorithm());
        btnCancel.addActionListener(new StopAlgorithm());
        btnCancel.setEnabled(false);
        btnKill.addActionListener(new KillAlgorithm());
        btnKill.setEnabled(false);
        
         
        headerPanel = new JPanel();
        concertPitchPanel = new JPanel();
        headerText = new JLabel("Set Configurations");
        headerText.setForeground(Color.BLUE);
        headerPanel.add(headerText);
        
        textConcertPitchWest.setBackground(concertPitchPanel.getBackground());
        textConcertPitchEast.setBackground(concertPitchPanel.getBackground());
        
        concertPitchPanel.add(textConcertPitchWest);
        concertPitchPanel.add(fieldConcertPitch);
        concertPitchPanel.add(textConcertPitchEast);      
        
        // Add Buttons
        panelFileBrowse.setLayout(new BorderLayout());
        panelFileBrowse.add(headerPanel, BorderLayout.NORTH);
        panelFileBrowse.add(lastFilePath, BorderLayout.SOUTH);
        panelFileBrowse.add(btnFileBrowser, BorderLayout.WEST);
        panelFileBrowse.add(concertPitchPanel, BorderLayout.EAST);
        panelFileBrowse.add(pnlSelectAlgorithmMethod, BorderLayout.CENTER);
        pnlSouth.add(btnRun);
        pnlSouth.add(btnCancel);
        pnlSouth.add(btnKill);
        pnlImage.setPreferredSize(new Dimension(500, 500));

        
        pnlCenter.add(pnlImage);
        pnlCenter.add(scrollPaneTracer);
        
        _mainFrame.setIconImage(LogoImage.loadImage(getClass().getResource(iconRelativePath)));
        _mainFrame.setPreferredSize(new Dimension(550, 800));
        
        // Setup Main Frame
        _mainFrame.getContentPane().setLayout(new BorderLayout());
        _mainFrame.getContentPane().add(panelFileBrowse, BorderLayout.NORTH);
        _mainFrame.getContentPane().add(pnlCenter, BorderLayout.CENTER);	
        _mainFrame.getContentPane().add(pnlSouth, BorderLayout.SOUTH);
        
        // Allows the Swing App to be closed
        _mainFrame.addWindowListener(new ListenCloseWdw());   
        
        //Add Menu listener
        menuItemQuit.addActionListener(new ListenMenuQuit());
        menuItemCompression.addActionListener(null);
        
        _notesSettingsConfig = new NotesSettings(_mainFrame);
        _compressionSettingsConfig = new CompressionSettings(_mainFrame);
        
        menuItemNotes.addActionListener(_notesSettingsConfig);
        menuItemCompression.addActionListener(_compressionSettingsConfig);
        
        menuItemAbout.addActionListener(new AboutWindow(_mainFrame));
        
    }
        
    class OpenFile implements ActionListener
    {
        public void actionPerformed(ActionEvent e)
        {
          JFileChooser chooser = new JFileChooser(_dir);
          
          // Use "Open" dialog:
          int rVal = chooser.showOpenDialog(_mainFrame);
          switch(rVal)
          {
          case JFileChooser.APPROVE_OPTION:
              _filename = chooser.getSelectedFile().getName();
              _dir = chooser.getCurrentDirectory().toString();
        	  break;
          case JFileChooser.CANCEL_OPTION:
        	  //	Don't do anything!        	  
        	  break;
          }
          lastFilePath.setText(getSelectedFilePath());
        }
      }
    
    class StopAlgorithm implements ActionListener
    {

		@Override
		public void actionPerformed(ActionEvent arg0)
		{
			if(worker != null)
			{
				
				analyzer.keepRunning = false;
			}
		}  
    }
    
    class KillAlgorithm implements ActionListener
    {

		@Override
		public void actionPerformed(ActionEvent arg0)
		{
			if(worker != null)
			{
				worker.cancel(false);
				analyzer.keepRunning = false;

			}
		}  
    }
    
    class RunAlgorithm implements ActionListener
    {

		@Override
		public void actionPerformed(ActionEvent e)
		{
			
			tracerClearAll();
			
			if(!isReady())
	    	{
	    		printMessage("Please select file to process first!");
	    		return;
	    	}
			else
			{
				printMessage("Processing... please wait!");
			}
			
			
			worker = new SwingWorker<List<SegmentNotesInfo>, Void>(){
				
				private List<SegmentNotesInfo> analysisResult;
				AnalyzeMusicFile.AnalysisPreferences selectedAnalysis;
				
				
			    @Override
			    public List<SegmentNotesInfo> doInBackground()
			    {
			    	
			    	String selectedAction = radioAlgorithmSelection.getSelection().getActionCommand();
			    	AlgorithmSettings selectedSettings;
			    	double tau;
			    	double delta;
			    	double windowSizeSec;
			    	double advanceFactorSec;
			    	int subSegments;	//	Number of sub-segments in each window
			    	
			    	double frequencyOfA;	//	Frequency of note "A" in Hz
			    	
			    	try
			    	{
			    		frequencyOfA = Double.parseDouble(fieldConcertPitch.getText());
			    	}
			    	catch(Exception e)
			    	{
			    		printMessage("\"A\" pitch entered is invalid! " + fieldConcertPitch.getText());
			    		return null;
			    	}
			    	try
			    	{
			    		selectedAnalysis = Enum.valueOf(AnalyzeMusicFile.AnalysisPreferences.class, selectedAction);
			    		selectedSettings = getSelectedConfigurationSettings(selectedAnalysis);
			    		if(selectedSettings == null)
			    		{
				    		throw new Exception();
			    		}
			    	}
			    	catch(Exception e)
			    	{
			    		printMessage("Configuration not supported: " + selectedAction);
			    		return null;
			    	}
			    	try
			    	{
			    		delta = selectedSettings.getDelta();
			    	}
			    	catch(Exception e)
			    	{
			    		printMessage("DELTA selected is invalid in " + selectedAction + " settings");
			    		return null;
			    	}
			    	try
			    	{
			    		tau = selectedSettings.getTau();
			    	}
			    	catch(Exception e)
			    	{
			    		printMessage("TAU selected is invalid in " + selectedAction + " settings");
			    		return null;
			    	}
			    	try
			    	{
			    		windowSizeSec = selectedSettings.getWindowSize();
			    	}
			    	catch(Exception e)
			    	{
			    		printMessage("Window size selected is invalid in " + selectedAction + " settings");
			    		return null;
			    	}
			    	try
			    	{
			    		advanceFactorSec = selectedSettings.getAdvanceFactor();
			    	}
			    	catch(Exception e)
			    	{
			    		printMessage("Advance factor selected is invalid in " + selectedAction + " settings");
			    		return null;
			    	}
			    	try
			    	{
			    		subSegments = selectedSettings.getSubSegments();
			    	}
			    	catch(Exception e)
			    	{
			    		printMessage("Number of sub-segments selected is invalid in " + selectedAction + " settings");
			    		return null;
			    	}
			    	//	Catch other illegal configurations:
			    	if(!selectedSettings.isValidConfiguration(delta, tau, windowSizeSec, advanceFactorSec, subSegments, selectedAnalysis))
			    	{
			    		printMessage("Algorithm settings are illegal in " + selectedAction + " settings");
			    		printMessage("If you have made configuration changes, please make sure you applied the changes!");
			    		return null;
			    	}
			    	
			    	btnRun.setEnabled(false);
					btnCancel.setEnabled(true);
					btnKill.setEnabled(true);
					analyzer = new AnalyzeMusicFile(getSelectedFilePath());
					//	Notes detection and compression require slightly different accuracy!
			    	analysisResult = analyzer.RunAnalysis(selectedAnalysis, tau, delta, 1e-3, windowSizeSec, advanceFactorSec, subSegments, frequencyOfA);
			    	if(analysisResult == null)
			    	{
			    		printMessage("An unknown failure has occured! Make sure selected file is valid");
			    		return null;	//	redundant...
			    	}
			    	return analysisResult;
			    }

				@Override
			    public void done()
			    {
					if(analysisResult == null)
					{
						selectedAnalysis = null;
					}
					if(selectedAnalysis != null)
					{
				    	switch(selectedAnalysis)
				    	{
				    	case Notes:
				    		printNoteDetectionResults(analysisResult);
				    		break;
				    	
				    	case Compression:
				    		decompressAndMeasureQuality(analysisResult);
				    		break;
				    	
				    	default:
				    			
				    		printMessage("Unknown command requested: " + selectedAnalysis.toString()
				    				);
				    		break;
				    	}
					}
			        
			        btnKill.setEnabled(false);
			        btnCancel.setEnabled(false);
					btnRun.setEnabled(true);
			    }

			    private AlgorithmSettings getSelectedConfigurationSettings(AnalysisPreferences selectedAnalysis) {
					switch(selectedAnalysis)
					{
					case Notes:
						return _notesSettingsConfig;
					case Compression:
						return _compressionSettingsConfig;
					default:
						return null;
					
					}
				}
				
			};
			
			//	Run calculation in a separate thread
			worker.execute();
		}

		/**
		 * This function handles the case of NOTES detection
		 * 
		 * @param segmentsInfo
		 */
		private void printNoteDetectionResults(List<SegmentNotesInfo> segmentsInfo)
		{
			for(SegmentNotesInfo segmentInfo : segmentsInfo)
			{
				printMessage("Segment #" + segmentInfo.getSegmentId());
				List<NoteInfo> foundFrequencies = segmentInfo.getNotesData();
				if(foundFrequencies == null)
				{
					printMessage("No data available");
					continue;
					
				}
				for(NoteInfo noteInfo : foundFrequencies)
				{
					Note currentNote = noteInfo.getNote();
					if(currentNote == Note.QuietSegment)
					{
						printMessage("QUIET SEGMENT");
					}
					else if(currentNote != Note.InvalidNote)
					{
						printMessage("Found note: " + noteInfo.getNote() + " " + noteInfo.getNoteFrequency() + " hz");
					}

				}
			}
		}
		
		/**
		 * This function handles the case of COMPRESSION
		 * 
		 * @param analysisResult
		 */
		private void decompressAndMeasureQuality(List<SegmentNotesInfo> analysisResult)
		{
			double windowLengthSec = analyzer.getWindowLength();
			long sampleRate = analyzer.getSampleRate();
			long framesPerSegment = analyzer.getNumberOfFramesPerWindow();
			String inputFileName = getSelectedFilePath();
			String outputFileName = inputFileName.substring(0, inputFileName.lastIndexOf('.')) + "_out.wav";
			
			printMessage("Decompressing file...");
			
			WavExport wavExporter = new WavExport(sampleRate, windowLengthSec);

			try {
				wavExporter.CreateWavFromCompressedData(analysisResult, sampleRate, windowLengthSec, framesPerSegment * analysisResult.size(), outputFileName);
			} catch (Exception e) {
				printMessage("Decompression failed :" + e.getMessage());
				return;
			}
			
			printMessage("Measuring error...");
			
			double errorMeasured;
			double inputAverageEnergy;
			double outputAverageEnergy;
			
			try
			{
				errorMeasured = CompressionQualityEstimation.CalcCompressionErrorNorm(inputFileName, outputFileName);
				inputAverageEnergy = CompressionQualityEstimation.CalcFileAverageNorm(inputFileName);
				outputAverageEnergy = CompressionQualityEstimation.CalcFileAverageNorm(outputFileName);
				
				printMessage("Average error norm calculated: " + errorMeasured);
				printMessage("Original file energy: " + inputAverageEnergy);
				printMessage("Output file energy: " + outputAverageEnergy);
				
			}
			catch (FileNotFoundException e)
			{
				printMessage("Failed measuring error: " + e.getMessage());
			}
			

			
			printMessage("Done!");
			
		}
    }
    
    public String getSelectedFilePath()
    {
  	  String currentFile = _filename.isEmpty() ? "" :  _dir + '\\' + _filename;
  	  
  	  return (new File(currentFile)).exists() ? currentFile : null;
  	  
    }
    

    
    /**
     * Determine if program is ready to run
     * 
     * @return
     */
    private boolean isReady()
    {
    	String selectedFile = getSelectedFilePath();
		return selectedFile != null && new File(getSelectedFilePath()).exists();
		
	}

	private void printMessage(String message)
	{
		//	We might want to redirect the output somewhere else later...
		tracerAddLine(message);
		
	}

	public class ListenMenuQuit implements ActionListener
	{
        public void actionPerformed(ActionEvent e)
        {
            System.exit(0);         
        }
    }
    
    public class ListenCloseWdw extends WindowAdapter
    {
        public void windowClosing(WindowEvent e)
        {
            System.exit(0);         
        }
    }
    
    public void launchFrame()
    {
        // Display Frame
    	_mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    	_mainFrame.pack(); //Adjusts panel to components for display
    	_mainFrame.setVisible(true);
    }
    
    private void tracerAddLine(String lineToAdd)
    {
    	//	Don't use it in the CTOR before item is initialized...
    	textAreaTracer.append(lineToAdd + "\n");
    }
    
    public void tracerClearAll()
    {
    	// 	Don't use it in the CTOR before item is initialized...
    	textAreaTracer.setText("");
    }
    
    public static void main(String args[])
    {	
        NotesJavaGUI gui = new NotesJavaGUI();
        gui.launchFrame();
    }
}